package game;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import org.json.JSONException;
import org.json.JSONObject;

import paintball.PaintballPacketInterpreter;
import paintball.PaintballPlayer;
import controllers.PlayerController;

/**
 * Paintball Player class
 * 
 * @author Twisty
 */
public class Player extends MovableGameObject implements Runnable {
	// Player name
	private String playerName;
	
	// Amount of health
	private int health;
	
	// Current vote
	private int vote = -1;

	// Whether the health is updated
	public boolean healthUpdated = false;
	
	// Thread this player runs in.
	private Thread thread;
	
	// Socket for stream to mobile phone.
	private Socket s;
	
	// Votes the player can cast.
	int[] votes = new int[]{1,2,3,4};

	// Last time pinging started.
	private long pingCurStartTime;
	// Last time pinging ended.
	private long pingCurEndTime;
	// Keep track if ping has been ponged back.
	private boolean ponged = false;
	// Amount of times the player has pinged to mobile phone.
	private int pingedAmount = 0;
	
	//Whether the ping timer needs to be restart
	private boolean restartPingTime = true;
	
	//Whether the player received a pong
	private boolean received = false;
	//Receiving timer start time
	private long recCurStartTime;
	//Receiving timer end time
	private long recCurEndTime;
	//Whether the receiving timer needs to be restart
	public boolean restartRecTime = true;

	
	/**
	 * Class constructor.
	 * @param socket	The socket used for connection to mobile phone.
	 */
	public Player(Socket socket) {
		super(-1337, -1337);
		this.s = socket;
		this.thread = new Thread(this);
		this.thread.start();
		
		this.setHeight(100);
		this.setWidth(100);
		if(PlayerController.currentGameState == Constants.votingMenu){
			// Send vote choices
			this.send(2);
			// Send the user to vote state
			this.send(4);
		} else {
			// Send the user to vote state
			this.send(4);
		}
	}

	/**
	 * Get the amount of health for current user.
	 * @return The current health
	 */
	public int getHealth() {
		return this.health;
	}

	/**
	 * Set a new health amount for current user.
	 * @param health		The new health amount
	 */
	public void setHealth(int health) {
		this.health = health;
		this.healthUpdated = true;
	}
	
	/**
	 * Get the ID the player has voted for.
	 * @return Vote ID.
	 */
	public int getVote(){
		return this.vote;
	}
	
	/**
	 * Set the vote ID.
	 * @param vote		The vote ID.
	 */
	public void setVote(int vote){
		this.vote = vote;
	}

	/**
	 * Set the player name.
	 * @param name		The new player name.
	 * 
	 */
	public void setName(String name) {
		this.playerName = name;
	}

	/**
	 * Get the player name.
	 * @return The current player name
	 */
	public String getPlayerName() {
		return this.playerName;
	}

	/**
	 * Sets ponged to true when ponged
	 */
	public void pong(){
		ponged = true;
	}
	
	/**
	 * Ping the mobile phone, reset ping stats.
	 */
	public void ping(){
		this.send(5);
		restartPingTime = true;
		this.pingedAmount++;
	}
	
	/**
	 * Set variables that a Pong has been received.
	 */
	public void checkReceived(){
		if(this.restartRecTime){
			this.recCurStartTime = System.currentTimeMillis();
			this.recCurEndTime = recCurStartTime + 500;
			restartRecTime = false;
		}
		if(recCurStartTime >= recCurEndTime){
			this.received = false;
			this.restartRecTime = true;
		}
		recCurStartTime++;
	}
	
	/**
	 * Start ping-pong procedure.
	 */
	public void pingPong(){
		//Ping pong
		if(this.restartPingTime){
			pingCurStartTime = System.currentTimeMillis();
			pingCurEndTime = pingCurStartTime+333;
			restartPingTime = false;
		}
		if(pingCurStartTime >= pingCurEndTime){
			switch(pingedAmount){
			case 0:
				ping();
				break;
			case 1:
				ping();
				break;
			case 2:
				if(ponged){
					ping();
					ponged = false;
					pingedAmount = 0;
				}else{
					disconnect("Timed Out");
				}
			}
			restartPingTime = true;
		}
		pingCurStartTime++;
	}
	
	/**
	 * Sets received to the given value
	 * @param received - the value to set received to
	 */
	public void setReceived(boolean received){
		this.received = received;
		if(received){
			this.pingedAmount = 0;
		}
	}
	
	/**
	 * @return - received
	 */
	public boolean getReceived(){
		return this.received;
	}
	
	/**
	 * Disconnect connection to mobile phone.
	 * @param reason	The reason for disconnecting.
	 */
	public void disconnect(String reason){
		this.setActive(false);
		System.out.println("Player " + getPlayerName() + " disconnected(" + reason + ")");
		PlayerController.removePlayer((PaintballPlayer) this);
	}
	
	@Override
	public void run() {
		BufferedReader inp = null;
		PaintballPacketInterpreter interpreter = new PaintballPacketInterpreter(this);
		String line;
		try {
			inp = new BufferedReader(new InputStreamReader(s.getInputStream()));
			// Attempt to receive data
			while ((line = inp.readLine()) != null) {
				JSONObject input = new JSONObject(line);
				interpreter.interpret(input);
				
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Send data to mobile phone.
	 * For communication codes:
	 * @see Constants
	 * 
	 * @param communicationCode 	The communication code to send to mobile phone.
	 */
	public void send(int communicationCode) {
		PrintWriter outp = null;
		try {
			outp = new PrintWriter(s.getOutputStream(), true);
			JSONObject jOb = createJsonData(communicationCode);
			if (jOb != null) {
				outp.println(jOb.toString());
	
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Send a JSON object to the mobile phone.
	 * @param jOb		The JSON object to be sent.
	 */
	public void sendObject(JSONObject jOb) {
		PrintWriter outp = null;
		try {
			outp = new PrintWriter(s.getOutputStream(), true);		
			if (jOb != null) {
				outp.println(jOb.toString());	
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Build data according to given communication code.
	 * @param communicationCode 	The communication code.
	 * @return The JSON object for given code.
	 */
	private JSONObject createJsonData(int communicationCode) {
		JSONObject jOb = new JSONObject();
		try {
			if (communicationCode == 0) {
				jOb.put("Code", communicationCode);
				jOb.put("Health", this.getHealth());
				this.healthUpdated = false;
			}
			else if(communicationCode == 1){
				jOb.put("Code", communicationCode);
				jOb.put("Powerup", 1);
			}
			else if(communicationCode == 2){
				jOb.put("Code", communicationCode);
				jOb.put("Game1", MainGame.votelist[0]);
				jOb.put("Game2", MainGame.votelist[1]);
				jOb.put("Game3", MainGame.votelist[2]);
			}
			else if (communicationCode == 4) {
				jOb.put("Code", communicationCode);
				System.out.println("sending state: " + PlayerController.currentGameState);
				jOb.put("State", PlayerController.currentGameState);
			}
			else if (communicationCode == 5){
				jOb.put("Code", communicationCode);
				jOb.put("Ping", true);
			}
			else {
				jOb = null;
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		return jOb;
	}

}
